package jdos.hardware;

import java.awt.event.KeyEvent;

import jdos.Dosbox;
import jdos.dos.Dos_programs;
import jdos.gui.Main;
import jdos.ints.Bios_keyboard;
import jdos.misc.Log;
import jdos.types.LogSeverities;
import jdos.types.LogTypes;

public class Keyboard {
static private final int KEYBUFSIZE = 32;
    static private final float KEYDELAY = 0.300f;			//Considering 20-30 khz serial clock and 11 bits/char

    static final private class KeyCommands {
        static public final int CMD_NONE=0;
    }

    static public final int ACMD_NONE=0;
	static public final int ACMD_SET_RATE=1;
	static public final int ACMD_SET_RESOLUTION=2;
    
    static private final int AUX = 0x100;

    /* Status Register Bits */
    private static final byte KBD_STAT_CMD = (byte)0x08; /* Last write was a command write (0=data) */
    private static final byte KBD_STAT_UNLOCKED = (byte)0x10; /* Zero if keyboard locked */

    private static final byte MM_STREAM=2;

    private static final byte MOUSE_NONE=0;
    private static final byte MOUSE_2BUTTON=1;
    private static final byte MOUSE_3BUTTON=2;
    private static final byte MOUSE_INTELLIMOUSE=3;
    private static final byte MOUSE_INTELLIMOUSE45=4;

    private static class ps2mouse {
        int	type;			/* what kind of mouse we are emulating */
        int	mode;			/* current mode */
        int[]		last_srate=new int[3];		/* last 3 "set sample rate" values */
        float		acx,acy;		/* accumulator */
        boolean		reporting;		/* reporting */
        boolean		intellimouse_mode;	/* intellimouse scroll wheel */
        boolean		intellimouse_btn45;	/* 4th & 5th buttons */
        boolean		int33_taken;		/* for compatability with existing DOSBox code: allow INT 33H emulation to "take over" and disable us */
        boolean		l,m,r;			/* mouse button states */
    }

    private static class Keyb {

        /*Bit8u*/int[] buffer=new int[KEYBUFSIZE];
        /*Bitu*/int used;
        /*Bitu*/int pos;

        ps2mouse ps2mouse = new ps2mouse();
        boolean auxchanged;
        boolean auxactive;
        boolean cb_irq12;			/* PS/2 mouse */
        boolean cb_irq1;

        /*Bit8u*/short p60data;
        boolean p60changed;
        boolean active;
        boolean scheduled;
        int status = KBD_STAT_CMD | KBD_STAT_UNLOCKED;
    }

    private static Keyb keyb = new Keyb();

    private static void KEYBOARD_SetPort60(/*Bit8u*/int val) {
        keyb.auxchanged=(val&AUX)>0;
        keyb.p60changed=true;
        keyb.p60data=(byte)(val & 0xFF);
        if (keyb.auxchanged) {
            if (keyb.cb_irq12) {
                Pic.PIC_ActivateIRQ(12);
            }
        } else if (keyb.cb_irq1) {
            Pic.PIC_ActivateIRQ(1);
        }
    }

    private static Pic.PIC_EventHandler KEYBOARD_TransferBuffer = new Pic.PIC_EventHandler() {
        public void call(/*Bitu*/int val) {
            keyb.scheduled=false;
            if (keyb.used==0) {
                Log.log(LogTypes.LOG_KEYBOARD, LogSeverities.LOG_NORMAL,"Transfer started with empty buffer");
                return;
            }
            KEYBOARD_SetPort60(keyb.buffer[keyb.pos]);
            if (++keyb.pos>=KEYBUFSIZE) keyb.pos-=KEYBUFSIZE;
            keyb.used--;
        }
        public String toString() {
            return "KEYBOARD_TransferBuffer";
        }
    };


    public static void KEYBOARD_ClrBuffer() {
        keyb.used=0;
        keyb.pos=0;
        Pic.PIC_RemoveEvents(KEYBOARD_TransferBuffer);
        keyb.scheduled=false;
    }

    private static void KEYBOARD_AddBuffer(/*Bit8u*/int data) {
    	System.out.println("key:"+Integer.toString(data, 16));
        if (keyb.used>=KEYBUFSIZE) {
            Log.log(LogTypes.LOG_KEYBOARD, LogSeverities.LOG_NORMAL,"Buffer full, dropping code");
            return;
        }
        /*Bitu*/int start=keyb.pos+keyb.used;
        if (start>=KEYBUFSIZE) start-=KEYBUFSIZE;
        keyb.buffer[start]=data;
        keyb.used++;
        /* Start up an event to start the first IRQ */
        if (!keyb.scheduled && !keyb.p60changed) {
            keyb.scheduled=true;
            Pic.PIC_AddEvent(KEYBOARD_TransferBuffer,KEYDELAY);
        }
    }

    private static IoHandler.IO_ReadHandler read_p60 = new IoHandler.IO_ReadHandler() {
        public /*Bitu*/int call(/*Bitu*/int port, /*Bitu*/int iolen) {
            keyb.p60changed=false;
            keyb.auxchanged = false;
            if (!keyb.scheduled && keyb.used!=0) {
                keyb.scheduled=true;
                Pic.PIC_AddEvent(KEYBOARD_TransferBuffer,KEYDELAY);
            }
            return keyb.p60data;
        }
    };

    static /*Bit8u*/short port_61_data = 0;
    private static IoHandler.IO_ReadHandler read_p61 = new IoHandler.IO_ReadHandler() {
        public /*Bitu*/int call(/*Bitu*/int port, /*Bitu*/int iolen) {
            port_61_data^=0x20;
            port_61_data^=0x10;
            return port_61_data;
        }
    };

    private static IoHandler.IO_WriteHandler write_p61 = new IoHandler.IO_WriteHandler() {
        public void call(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
            if (((port_61_data ^ val) & 3)!=0) {
                if(((port_61_data ^ val) & 1)!=0) Timer.TIMER_SetGate2((val&0x1)!=0);
                PCSpeaker.PCSPEAKER_SetType(val & 3);
            }
            port_61_data = (short)val;
        }
    };

    private static IoHandler.IO_ReadHandler read_p64 = new IoHandler.IO_ReadHandler() {
        public /*Bitu*/int call(/*Bitu*/int port, /*Bitu*/int iolen) {
            return keyb.status | (keyb.p60changed? 0x1 : 0x0);
        }
    };

	static public void KEYBOARD_AddKey(KeyEvent e) {
		int keytype = e.getKeyCode();
		int location = e.getKeyLocation();
		boolean pressed = e.getID() == KeyEvent.KEY_PRESSED;
	
        /*Bit8u*/short ret=0;boolean extend=false;
        switch (keytype) {
        case KeyEvent.VK_ESCAPE:ret=1;break;
        case KeyEvent.VK_1:ret=2;break;
        case KeyEvent.VK_2:ret=3;break;
        case KeyEvent.VK_3:ret=4;break;
        case KeyEvent.VK_4:ret=5;break;
        case KeyEvent.VK_5:ret=6;break;
        case KeyEvent.VK_6:ret=7;break;
        case KeyEvent.VK_7:ret=8;break;
        case KeyEvent.VK_8:ret=9;break;
        case KeyEvent.VK_9:ret=10;break;
        case KeyEvent.VK_0:ret=11;break;
        case KeyEvent.VK_MINUS:ret=(short) ((location == 3) ? 12 : 74);break;
        case KeyEvent.VK_EQUALS:ret=13;break;
        case KeyEvent.VK_BACK_SPACE:ret=14;break;

        case KeyEvent.VK_TAB:ret=15;break;
        case KeyEvent.VK_Q:ret=16;break;
        case KeyEvent.VK_W:ret=17;break;
        case KeyEvent.VK_E:ret=18;break;
        case KeyEvent.VK_R:ret=19;break;
        case KeyEvent.VK_T:ret=20;break;
        case KeyEvent.VK_Y:ret=21;break;
        case KeyEvent.VK_U:ret=22;break;
        case KeyEvent.VK_I:ret=23;break;
        case KeyEvent.VK_O:ret=24;break;
        case KeyEvent.VK_P:ret=25;break;
        case KeyEvent.VK_OPEN_BRACKET:ret=26;break;
        case KeyEvent.VK_CLOSE_BRACKET:ret=27;break;
        case KeyEvent.VK_ENTER:extend=location == 4;ret=28;break;
        case KeyEvent.VK_CONTROL:extend=location == 3;ret=29;break;

        case KeyEvent.VK_A:ret=30;break;
        case KeyEvent.VK_S:ret=31;break;
        case KeyEvent.VK_D:ret=32;break;
        case KeyEvent.VK_F:ret=33;break;
        case KeyEvent.VK_G:ret=34;break;
        case KeyEvent.VK_H:ret=35;break;
        case KeyEvent.VK_J:ret=36;break;
        case KeyEvent.VK_K:ret=37;break;
        case KeyEvent.VK_L:ret=38;break;

        case KeyEvent.VK_SEMICOLON:ret=39;break;
        case KeyEvent.VK_QUOTE:ret=40;break;
        case KeyEvent.VK_DEAD_GRAVE:ret=41;break;
		case KeyEvent.VK_SHIFT: ret = (short) ((location == 3) ? 54 : 42);break;
        case KeyEvent.VK_BACK_SLASH:ret=43;break;
        case KeyEvent.VK_Z:ret=44;break;
        case KeyEvent.VK_X:ret=45;break;
        case KeyEvent.VK_C:ret=46;break;
        case KeyEvent.VK_V:ret=47;break;
        case KeyEvent.VK_B:ret=48;break;
        case KeyEvent.VK_N:ret=49;break;
        case KeyEvent.VK_M:ret=50;break;

        case KeyEvent.VK_COMMA:ret=51;break;
        case KeyEvent.VK_PERIOD:ret=52;break;
        case KeyEvent.VK_SLASH:ret=53;break;
        case KeyEvent.VK_ALT:extend=location == 3;ret=56;break;
        case KeyEvent.VK_SPACE:ret=57;break;
        case KeyEvent.VK_CAPS_LOCK:ret=58;break;

        case KeyEvent.VK_F1:ret=59;break;
        case KeyEvent.VK_F2:ret=60;break;
        case KeyEvent.VK_F3:ret=61;break;
        case KeyEvent.VK_F4:ret=62;break;
        case KeyEvent.VK_F5:ret=63;break;
        case KeyEvent.VK_F6:ret=64;break;
        case KeyEvent.VK_F7:ret=65;break;
        case KeyEvent.VK_F8:ret=66;break;
        case KeyEvent.VK_F9:ret=67;break;
        case KeyEvent.VK_F10:ret=68;break;

        case KeyEvent.VK_NUM_LOCK:ret=69;break;
        case KeyEvent.VK_SCROLL_LOCK:ret=70;break;

        case KeyEvent.VK_MULTIPLY:ret=55;break;
        case KeyEvent.VK_NUMPAD7:ret=71;break;
        case KeyEvent.VK_NUMPAD8:ret=72;break;
        case KeyEvent.VK_NUMPAD9:ret=73;break;
        case KeyEvent.VK_NUMPAD4:ret=75;break;
        case KeyEvent.VK_NUMPAD5:ret=76;break;
        case KeyEvent.VK_NUMPAD6:ret=77;break;
        case KeyEvent.VK_PLUS:ret=78;break;
        case KeyEvent.VK_NUMPAD1:ret=79;break;
        case KeyEvent.VK_NUMPAD2:ret=80;break;
        case KeyEvent.VK_NUMPAD3:ret=81;break;
        case KeyEvent.VK_NUMPAD0:ret=82;break;
        case KeyEvent.VK_DELETE:extend=location == 3;ret=83;break;

        //case KBD_KEYS.KBD_extra_lt_gt:ret=86;break;
        case KeyEvent.VK_F11:ret=87;break;
        case KeyEvent.VK_F12:ret=88;break;

        //The Extended keys

        case KeyEvent.VK_DIVIDE:extend=true;ret=53;break;
        case KeyEvent.VK_HOME:extend=true;ret=71;break;
        
        case KeyEvent.VK_KP_UP:extend=true;ret=72;break;
        case KeyEvent.VK_KP_DOWN:extend=true;ret=80;break;
        case KeyEvent.VK_KP_LEFT:extend=true;ret=75;break;
        case KeyEvent.VK_KP_RIGHT:extend=true;ret=77;break;
        
        case KeyEvent.VK_UP:ret=72;break;
        case KeyEvent.VK_DOWN:ret=80;break;
        case KeyEvent.VK_LEFT:ret=75;break;
        case KeyEvent.VK_RIGHT:ret=77;break;
  
//        case KBD_KEYS.KBD_pageup:extend=true;ret=73;break;
//        case KBD_KEYS.KBD_end:extend=true;ret=79;break;
//        case KBD_KEYS.KBD_down:extend=true;ret=80;break;
//        case KBD_KEYS.KBD_pagedown:extend=true;ret=81;break;
//        case KBD_KEYS.KBD_insert:extend=true;ret=82;break;

        default:
            //Log.exit("Unsupported key press");
            break;
        }
        /* Add the actual key in the keyboard queue */
		if (keyb != null) { // keyb is  null when WINE is running
			if (pressed) {
			} else {
				ret += 128;
			}
			if (extend) {
				KEYBOARD_AddBuffer(0xe0);
			}
			KEYBOARD_AddBuffer(ret);

		}
	}

    public static void KEYBOARD_Init() {
        keyb = new Keyb();
        //if (keyb.enable_aux=section.Get_bool("aux")) {
        //    Log.log(LogTypes.LOG_KEYBOARD, LogSeverities.LOG_NORMAL,"Keyboard AUX emulation enabled");
        //}
        keyb.ps2mouse.int33_taken = false;

        String sbtype="2button";//section.Get_string("auxdevice");
        keyb.ps2mouse.type = MOUSE_NONE;
        if (sbtype != null) {
            if (sbtype.equals("2button"))
                keyb.ps2mouse.type=MOUSE_2BUTTON;
            else if (sbtype.equals("3button"))
                keyb.ps2mouse.type=MOUSE_3BUTTON;
            else if (sbtype.equals("intellimouse"))
                keyb.ps2mouse.type=MOUSE_INTELLIMOUSE;
            else if (sbtype.equals("intellimouse45"))
                keyb.ps2mouse.type=MOUSE_INTELLIMOUSE45;
            else if (sbtype.equals("none"))
                keyb.ps2mouse.type=MOUSE_NONE;
            else {
                keyb.ps2mouse.type=MOUSE_INTELLIMOUSE;
                Log.log(LogTypes.LOG_KEYBOARD, LogSeverities.LOG_ERROR, "Assuming PS/2 intellimouse, I don't know what '"+sbtype+"' is");
            }
        }
        IoHandler.IO_RegisterReadHandler(0x60,read_p60,IoHandler.IO_MB);
        IoHandler.IO_RegisterWriteHandler(0x61,write_p61,IoHandler.IO_MB);
        IoHandler.IO_RegisterReadHandler(0x61,read_p61,IoHandler.IO_MB);
        IoHandler.IO_RegisterReadHandler(0x64,read_p64,IoHandler.IO_MB);
        write_p61.call(0,0,0);
        KEYBOARD_Reset();
    }

    static public void AUX_INT33_Takeover() {
        if (keyb.ps2mouse.type != MOUSE_NONE && keyb.ps2mouse.int33_taken)
            Log.log(LogTypes.LOG_KEYBOARD, LogSeverities.LOG_NORMAL,"PS/2 mouse emulation: Program is using INT 33h, disabling direct AUX emulation");
        keyb.ps2mouse.int33_taken = true;
    }

    static private void KEYBOARD_Reset() {
        /* Init the keyb struct */
        keyb.active=true;
        //keyb.pending_key=-1;
        keyb.auxactive=false;
        //keyb.pending_key_state=false;
        keyb.p60changed=false;
        keyb.auxchanged=false;
        /* command byte */
        keyb.cb_irq12=false;
        keyb.cb_irq1=true;
        /* OK */
        KEYBOARD_ClrBuffer();
    }

}
